package com.yunxin.core.utils.infra.utils;


import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class ChannelFlowRateUtil {


    /**
     * 渠道小时总量
     */
    private static final String HOUR_TOTAL_KEY_TPL = "flow:{}:total";


    /**
     * 渠道小时计量
     */
    private static final String HOUR_CTOTAL_KEY_TPL = "flow:{}:{}:ctotal";


    /**
     * 渠道每秒进量最大值
     */
    private static final String SECOND_SMAX_KEY_TPL = "flow:{}:smax";


    /**
     * 渠道当前秒数进量
     */
    private static final String SECOND_SCNUM_KEY_TPL = "flow:{}:{}:{}:scnum";


    /**
     * 执行失败
     */
    private static final Long FAIL = 0L;
    /**
     * 执行成功
     */
    private static final Long SUCCEED = 1L;

    private static final Logger log = LoggerFactory.getLogger(ChannelFlowRateUtil.class);

    /**
     * 设置 或者 初始化 限流参数
     * 直接设置值
     */
    private static String SET_OR_INIT_FLOW_ARGS = """
            redis.call('set',KEYS[1],ARGV[1])
            redis.call('set',KEYS[2],ARGV[2])
            return 1
            """;


    /**
     * 限流
     */

    private static String FLOW_SCRIPT = """
            local hour_total=redis.call('get',KEYS[1])\s
            local second_max=redis.call('get',KEYS[3])
            if hour_total then\s
            	redis.call('set',KEYS[2],0,'ex','3600','nx')\s
            	
            	local chour_total=redis.call('get',KEYS[2])\s
                
                if tonumber(hour_total) == 0 then
                   return 1
                end 
            	if chour_total and tonumber(chour_total) >=tonumber(hour_total) then
            		return 0
            	end\s
            	
            	local SET_STATE= redis.call('set',KEYS[4],0,'ex','61','nx') \s
            	local second_num= 0
            	
            	if tonumber(SET_STATE) ~= 1 then\s
            	  second_num=tonumber(redis.call('get',KEYS[4]))
            	end\s

            	if tonumber(second_num) < tonumber(second_max) then\s
            		redis.call('incr',KEYS[2])
            		redis.call('incr',KEYS[4])
            		return 1
            	end		
            	return 0
            end
            return 1
            """;



    @Autowired
    private RedisTemplate<String,Object> redisTemplate;



    /**
     * 设置值 或 初始化
     * @param channel 渠道code
     * @param total 每小时多少量
     * @return
     */
    public Boolean setOrInit(String channel, Long total){

        int hour = DateUtil.date().getField(DateField.HOUR_OF_DAY);


        //每秒最大值
        int secondsMax =1;

        if (total > 0L && total < 60L) {
            secondsMax = 1;
        } else {
            secondsMax = PageUtil.totalPage(total, 60);
        }

        DefaultRedisScript<Long> script = new DefaultRedisScript<>(SET_OR_INIT_FLOW_ARGS, Long.class);


        List<String> keys = List.of(
                StrUtil.format(HOUR_TOTAL_KEY_TPL, channel),
                StrUtil.format(SECOND_SMAX_KEY_TPL, channel)
        );


        try {
            Long execute = redisTemplate.execute(script, keys, total, secondsMax);
            return SUCCEED.equals(execute);
        } catch (Exception e) {
           log.error("channel={},渠道限流参数操作失败,e=",channel,e);
            return false;
        }


    }


    /**
     * 限流
     * @param channel
     * @return 可以通过返回true
     */
    public Boolean flow(String channel){

        int hour = DateUtil.date().getField(DateField.HOUR_OF_DAY);
        int second = DateUtil.date().getField(DateField.MINUTE);

        DefaultRedisScript<Long> script = new DefaultRedisScript<>(FLOW_SCRIPT, Long.class);
        List<String> keys = List.of(
                StrUtil.format(HOUR_TOTAL_KEY_TPL, channel),
                StrUtil.format(HOUR_CTOTAL_KEY_TPL, channel, hour),
                StrUtil.format(SECOND_SMAX_KEY_TPL, channel),
                StrUtil.format(SECOND_SCNUM_KEY_TPL, channel, hour,second)
        );


        try {
            Object[] args = new Object[0];
            Long execute = redisTemplate.execute(script, keys, args);
            return SUCCEED.equals(execute);
        } catch (Exception e) {
            log.error("channel={},渠道限流操作失败,e=",channel,e);
            return false;
        }


    }



}
